ShowTable of Contents
Introduction
This article is part of the
XPages Extensibility API Developers Guide.
This article shows you step-by-step (with screenshots and code samples) how to create a Java control for XPages in an NSF application. Using a control in an NSF application can be useful if you do not need to share the control between different applications so that you do not need an XPages library. It also has the advantage that there is no need for a deploy step. If you are developing a library of controls, prototyping them in an NSF application is convenient because it gives you immediate feedback than constantly rebuilding the library plugin and re-installing it into Domino Designer.
Create Java control class
Set up your application
In Domino Designer, select File - New - Application. Create a new application on the server called "controlDev".
It's easier to debug applications on the server since you can use println that go to the server console. Select File - Application - Properties.
In Application Properties, select the XPages tab and in the Errors and Timeouts section, enable the check box "Display XPages runtime error page".
This means you get stack traces in the web browser instead of just Error 500.
Open the Package Explorer view
Select Window - Show Eclipse Views - Other... - Java - Package Explorer.
If you want you can use the entire Java perspective to edit the Java files. Select Window - Open Perspective - Other... - Java.
Use the same menu to go back to the Domino Designer perspective or use Ctrl+F8 to switch between perspectives.
Create the source folder
In the Package Explorer view, right-click on the application and select New - Other... - Java - Source Folder - Next. Enter the folder name "WebContent/WEB-INF/src" and Finish.
We're using this new source folder instead of the existing Local source folder because it's not saved in the NSF. The Local folder is saved in your workspace and its contents are lost when you move the NSF to a different machine or send it to somebody.
Write the Java control class
In the Package Explorer view, right-click on the source folder and select New - Other... - Java - Class - Next.
Enter the following package, name and superclass and Finish.
Package: com.example.component
Name: ExampleControl
Superclass: javax.faces.component.UIComponentBase
The generated class is like so:
package com.example.component;
import javax.faces.component.UIComponentBase;
public class ExampleControl extends UIComponentBase {
@Override
public String getFamily() {
// TODO Auto-generated method stub
return null;
}
}
Leave the
getFamily method as it is for now, we'll come back to it when creating a renderer.
Create / Register control tag
Create an xsp-config file defining the control
Next, you will want to use your control in an XPage. For that you have to register the control tag in an xsp-config configuration file.
In the Package Explorer view, right-click the folder "WebContent/WEB-INF" (not the Java source folder previously created) and select New - Other... - General - File - Next. Enter the file name "exampleControl.xsp-config" and Finish.
Then copy and paste the following code into this file.
<faces-config>
<faces-config-extension>
<namespace-uri>http://example.com/xsp/control</namespace-uri>
<default-prefix>eg</default-prefix>
</faces-config-extension>
<component>
<description>An example control used in
explaining how to write your own controls.</description>
<display-name>Example Control</display-name>
<component-type>com.example.examplecontrol</component-type>
<component-class>com.example.component.ExampleControl</component-class>
<component-extension>
<component-family>com.example.examplecontrol</component-family>
<renderer-type>com.example.examplecontrol</renderer-type>
<tag-name>exampleControl</tag-name>
</component-extension>
</component>
</faces-config>
Update the
namespace-uri,
default-prefix and
tag-name to what you want to use for your control. The
component-class should refer to the Java class you created.
Note, when you make changes to an xsp-config file it rebuilds all the XPages in the application in case some of the XPages depend on the definition in the xsp-config file. If you want to change that behavior there is a preference.
From the menu bar, select File - Preferences - Domino Designer - XPages. There is an "Automatically recompile" option which you can change to "Manually recompile". The option was originally added for changes to Custom Controls but also applies to changes to xsp-config files.
Verify the xsp-config file is read
Create an XPage and type in some text. In the Controls palette, select Other... - Other Controls - Example Control - OK.
The control won't be present if there was some problem reading the xsp-config file. See the next section for debugging xsp-config problems.
In the Java perspective, after you have added the control to an XPage, you can open the compiled XPage (controlDev.nsf/Local/xsp/*.java) to see the code where it creates your control class.
private UIComponent createExamplecontrol1(FacesContext context,
UIComponent parent, PageExpressionEvaluator evaluator) {
ExampleControl result = new ExampleControl();
setId(result, "exampleControl1");
return result;
}
At this stage, if you open the XPage in a browser or the Notes Client, you will see any text that you typed into the XPage but your new control does not generate any output because you have not set up any runtime renderer for the control.
Debug xsp-config problems
You need to see the Domino Designer console output. From the menu bar, select Help - Support - View Trace and scroll to the end to find the problem. For example, if you had a spelling error in the control's class name, you would see a warning like this.
CLFAD0090W: The component-class (com.example.component.ExampleCtrl) could not be resolved for the component with component-type com.example.examplecontrol.
If you have frequent problems then re-opening the View Trace can get annoying. That file can be found in the Notes directory at "\Data\workspace\logs\trace-log-0.xml". It's XML-based, difficult to scan and browsers complain about unmatched tags. The best way to see the errors and warnings is to display the console when Domino Designer is launched. To do so, just edit the shortcut you use to launch Domino Designer and append "-RPARAMS -console". When you re-launch Designer you'll see a console behind it, where the stack traces are output.
e.g. C:\Program Files\Lotus\Notes\designer.exe "=C:\Program Files\Lotus\Notes\notes.ini" -RPARAMS -console
Create control renderer
Using a renderer for the control
A renderer class is used to output some HTML corresponding to the control to be displayed in the browser or in the Notes client.
The mapping from
UIComponent to renderer is done by means of the UIComponent's
getFamily method and
rendererType value and the faces-config file that lists the
renderer-class for a given
component-family and
renderer-type. The JSF runtime reads the faces-config file, creates the renderer instances and the
UIComponentBase superclass invokes the renderer. Each renderer has a single instance per JVM, so they should not have any local variables, per-control values should be saved in your
UIComponentBase subclass.
For this example we will make up a new family as we're building a simple control that is not related to any other controls. When you build more advanced controls you should familiarize yourself with the JSF control inheritance tree and the meanings of the different UI base classes, to figure out which kind of control you are building and hence which base class you should extend from and which family should be used for the renderer.
Related controls are grouped into families. For example, the Combo Box, Radio Button Group and non-multi-select ListBox controls are all grouped into the SelectOne family and extend from the UISelectOne base class because they allow the end user to select one value from a range of options.
Likewise we will invent a new
renderer-type. The
renderer-type is generally unique to a control, and the renderer implementation will usually handle one control class. If you had multiple controls with very similar functionality you might use the same renderer to output their HTML. In that case, the renderer could be registered in the faces-config file multiple times, with different component family values and the renderer implementation would handle different types of controls passed to the encode methods.
Update the Java control class
Decide the
renderer-type and
component-family you will use to identify the renderer. The convention is to use a namespace-like prefix for both. Initialize the renderer-type in the constructor and provide the component-family in the
getFamily method.
package com.example.component;
import javax.faces.component.UIComponentBase;
public class ExampleControl extends UIComponentBase {
public ExampleControl() {
super();
setRendererType("com.example.examplecontrol");
}
@Override
public String getFamily() {
return "com.example.examplecontrol";
}
}
Create a renderer class
In the Package Explorer view, right-click on the source folder and select New - Other... - Java - Class - Next.
Enter the following package name, class name and superclass and Finish.
Package: com.example.renderkit.html_basic
Name: ExampleRenderer
Superclass: javax.faces.render.Renderer
Then copy and paste the following code into this file.
package com.example.renderkit.html_basic;
import java.io.IOException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;
public class ExampleRenderer extends Renderer {
@Override
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException {
// Note, the component passed in is the ExampleControl
// ExampleControl control = (ExampleControl) component;
ResponseWriter writer = context.getResponseWriter();
writer.startElement("div", component);
writer.writeAttribute("style", "border:orange solid thin", null);
writer.writeText("This is an ExampleControl using the renderer", null);
writer.startElement("hr", null);
writer.endElement("hr");
}
@Override
public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.endElement("div");
}
}
This renderer outputs a <div> tag with an orange border, some text and a horizontal rule. If the XPage using the control contains any text or controls between the <eg:exampleControl> start and end tags, those contents will be output after the
encodeBegin and before the
encodeEnd so it will appear in the browser just before the </div> close tag.
Register the control renderer in a faces-config file
The faces-config file will map from the UIComponent to the renderer class. The file already exists in the application at "controlDev.nsf/WebContent/WEB-INF/faces-config.xml". This file is a standard JSF (JavaServer Faces) faces-config file.
Copy and paste the following code into this file.
<faces-config>
<render-kit>
<renderer>
<component-family>com.example.examplecontrol</component-family>
<renderer-type>com.example.examplecontrol</renderer-type>
<renderer-class>com.example.renderkit.html_basic.ExampleRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
The render-kit does not have a render-kit id so the renderer will go into the default HTML_BASIC renderkit which is used on the web. Your renderer will also be used in the Notes Client as the Notes render-kit HTML_RCP inherits from HTML_BASIC. If you wished to provide a renderer only used in the Notes client, you would specify the HTML_RCP render-kit-id.
Open the XPage in a web browser and verify that the renderer output appears as expected.
For problems in the faces-config file
You can insert this
DOCTYPE after the faces-config <?xml line:
The editor will tell you if your contents don't match the DTD. Such errors only appear in the editor itself, not in the Problems view. You will want to remove that
DOCTYPE part before shipping the application to prevent unnecessary round-trips to the server at runtime.
For problems with the class resolution in the faces-config files, you'll have to look at the Domino server console. Errors like this indicate that something went wrong parsing a faces-config file but don't tell you the exact problem.
java.lang.ClassCastException: com.sun.faces.application.ApplicationImpl incompatible with com.ibm.xsp.application.ApplicationEx
Scroll further up the server console to find the initial cause. [SPR#MKEE84CLVZ asks for a more understandable error message instead of that ApplicationImpl exception.]
For example, if you set the
renderer-class to a UIComponent instead of a renderer, you'll get an error like this.
java.lang.ClassCastException: com.example.component.ExampleControl incompatible with javax.faces.render.Renderer
Because the faces-config files are loaded statically, when you fix an error it doesn't always detect that it should reload the faces-config file. So you may have to enter the following command on the Domino server console.
tell http restart
Classloader problems at runtime
As you edit the Java classes in the NSF and preview to see what your changes are like, the underlying compiled class files change. The Domino server and Notes client will attempt to discard the existing Java class objects they have loaded to create an updated class object containing the changes you have made. However occasionally, an object created from the old class object will hang around and you may get errors like this.
java.lang.ClassCastException: com.example.Something incompatible with com.example.Something
Where both incompatible classes have the same name or other class related to the problems, like if it believes a class does not implement an interface when it actually does. Such problems only occur when you are editing the Java classes so they should not occur in a production environment (assuming you test and edit applications in a separate test environment). Those issues were partially fixed for SPR#MKEE82DFR8 but some problems may still occur. If you encounter that problem, workarounds are:
- Make some change to the faces-config.xml file within the application. This causes the runtime application object to be discarded so if the object using the old class was saved in the application or in the user session within the application, the offending object will be gone.
- If that does not work, try entering the following on the Domino server console.
- If the problem still occurs, restart the Domino server.
In 8.5.3, there have been further updates to the runtime classloader handling for SPR#PHAN8HFRQU.
Example files
Download the
example files archive. To use this example, create a new application.
In the Package Explorer view, right-click on the new application and select Import - General - Archive File - Next.
Browse to the example files and select Finish. When asked to overwrite existing files, select "Yes To All".